/*
 * Copyright 2007-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package jsr203.sun.nio.fs;

import java.io.FileDescriptor;
import java.nio.channels.FileChannel;
import java.util.Set;
import jsr203.nio.file.OpenOption;
import jsr203.nio.file.StandardOpenOption;
import static jsr203.sun.nio.fs.UnixConstants.EEXIST;
import static jsr203.sun.nio.fs.UnixConstants.EISDIR;
import static jsr203.sun.nio.fs.UnixConstants.ELOOP;
import static jsr203.sun.nio.fs.UnixConstants.O_APPEND;
import static jsr203.sun.nio.fs.UnixConstants.O_CREAT;
import static jsr203.sun.nio.fs.UnixConstants.O_DSYNC;
import static jsr203.sun.nio.fs.UnixConstants.O_EXCL;
import static jsr203.sun.nio.fs.UnixConstants.O_NOFOLLOW;
import static jsr203.sun.nio.fs.UnixConstants.O_RDONLY;
import static jsr203.sun.nio.fs.UnixConstants.O_RDWR;
import static jsr203.sun.nio.fs.UnixConstants.O_SYNC;
import static jsr203.sun.nio.fs.UnixConstants.O_TRUNC;
import static jsr203.sun.nio.fs.UnixConstants.O_WRONLY;
import static jsr203.sun.nio.fs.UnixNativeDispatcher.openat;
import sun.misc.JavaIOFileDescriptorAccess;
import sun.misc.SharedSecrets;
import sun.nio.ch.FileChannelImpl;

/**
 * Factory for FileChannels and AsynchronousFileChannels
 */

class UnixChannelFactory {
    private static final JavaIOFileDescriptorAccess fdAccess =
        SharedSecrets.getJavaIOFileDescriptorAccess();

    private UnixChannelFactory() {
    }

    static FileChannel newFileChannel(int fd, boolean reading, boolean writing) {
        FileDescriptor fdObj = new FileDescriptor();
        fdAccess.set(fdObj, fd);
        return FileChannelImpl.open(fdObj, reading, writing, null);
    }

    static FileChannel newFileChannel(int dfd,
                                      byte[] pathForSysCall,
                                      String pathForPermissionCheck,
                                      Set<? extends OpenOption> options,
                                      int mode)
        throws UnixException
    {
        boolean reading = false;
        boolean writing = false;
        boolean append = false;
        boolean trunc = false;

        // check for invalid flags
        for (OpenOption flag: options) {
            if (flag == StandardOpenOption.READ) {
                reading = true; continue;
            }
            if (flag == StandardOpenOption.WRITE) {
                writing = true; continue;
            }
            if (flag == StandardOpenOption.APPEND) {
                append = true;
                writing = true;
                continue;
            }
            if (flag == StandardOpenOption.TRUNCATE_EXISTING) {
                trunc = true; continue;
            }
            if (flag == null)
                throw new NullPointerException();
            if (!(flag instanceof StandardOpenOption))
                throw new IllegalArgumentException();
        }

        // default is reading
        if (!reading && !writing) {
            reading = true;
        }

        // check for invalid combinations
        if (reading && append)
            throw new IllegalArgumentException("READ + APPEND not allowed");
        if (append && trunc)
            throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");

        FileDescriptor fdObj = open(dfd, pathForSysCall, pathForPermissionCheck,
            reading, writing, append, options, mode);
        return FileChannelImpl.open(fdObj, reading, writing, null);
    }

    static FileChannel newFileChannel(byte[] pathForSysCall,
                                      String pathForPermissionCheck,
                                      Set<? extends OpenOption> options,
                                      int mode)
        throws UnixException
    {
        return newFileChannel(-1, pathForSysCall, pathForPermissionCheck,
            options, mode);
    }

    /**
     * Opens file based on parameters and options, returning a FileDescriptor
     * encapsulating the handle to the open file.
     */
    static FileDescriptor open(int dfd,
                               byte[] pathForSysCall,
                               String pathForPermissionCheck,
                               boolean reading,
                               boolean writing,
                               boolean append,
                               Set<? extends OpenOption> options,
                               int mode)
        throws UnixException
    {
        // will be true if CREATE_NEW specified
        boolean createNew = false;

        // map to oflags
        int oflags;
        if (reading && writing) {
            oflags = O_RDWR;
        } else {
            oflags = (writing) ? O_WRONLY : O_RDONLY;
        }
        if (writing) {
            if (options.contains(StandardOpenOption.TRUNCATE_EXISTING))
                oflags |= O_TRUNC;
            if (append)
                oflags |= O_APPEND;

            // create flags
            if (options.contains(StandardOpenOption.CREATE_NEW)) {
                // throw exception if file name is "." to avoid confusing error
                if ((pathForSysCall[pathForSysCall.length-1] == '.') &&
                    (pathForSysCall.length == 1 ||
                    (pathForSysCall[pathForSysCall.length-2] == '/')))
                {
                    throw new UnixException(EEXIST);
                }
                oflags |= (O_CREAT | O_EXCL);
                createNew = true;
            } else {
                if (options.contains(StandardOpenOption.CREATE))
                    oflags |= O_CREAT;
            }
        }

        // follow links by default
        boolean followLinks = true;
        if (!createNew && options.contains(StandardOpenOption.NOFOLLOW_LINKS)) {
            followLinks = false;
            oflags |= O_NOFOLLOW;
        }

        if (options.contains(StandardOpenOption.DSYNC)) oflags |= O_DSYNC;
        if (options.contains(StandardOpenOption.SYNC)) oflags |= O_SYNC;

        // permission check before we open the file
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            if (reading)
                sm.checkRead(pathForPermissionCheck);
            if (writing)
                sm.checkWrite(pathForPermissionCheck);
        }

        int fd;
        try {
            if (dfd >= 0) {
                fd = openat(dfd, pathForSysCall, oflags, mode);
            } else {
                fd = UnixNativeDispatcher.open(pathForSysCall, oflags, mode);
            }
        } catch (UnixException x) {
            // Linux error can be EISDIR or EEXIST when file exists
            if (createNew && (x.errno() == EISDIR)) {
                x.setError(EEXIST);
            }

            // handle ELOOP to avoid confusing message
            if (!followLinks && (x.errno() == ELOOP)) {
                x = new UnixException(x.getMessage() + " (NOFOLLOW_LINKS specified)");
            }

            throw x;
        }

        // create java.io.FileDescriptor
        FileDescriptor fdObj = new FileDescriptor();
        fdAccess.set(fdObj, fd);
        return fdObj;
    }
}
